﻿using System;
using System.Collections.Generic;
using Jayden.Dll.Core.Persistence.Data;
using Jayden.Dll.Core.Persistence.Enums;
using Jayden.Dll.Core.Persistence.Models;

namespace Jayden.Dll.Core.Persistence
{
	public class UnitOfWork : IDisposable
	{
		private Configuration m_Configuration;

		private IdentityMap m_IdentityMap = new IdentityMap();
		private List<Object> m_NewObjects = new List<Object>();
		private List<Object> m_DirtyObjects = new List<Object>();
		private List<Object> m_DeletedObjects = new List<Object>();

		private DbConnectionManager m_DbConnectionManager;
		private DataAccessManager m_DataAccessManager;
		private Model m_Model;
		private Factory m_Factory;

		public UnitOfWork(Configuration config)
		{
			if (config == null)
				throw new ArgumentNullException("config");
			m_Configuration = config;
			m_Factory = new Factory(config.GetConfiguration("Factory"));
			m_Model = new Model(config.GetConfiguration("Model"));
			m_DbConnectionManager = new DbConnectionManager(config.GetConfiguration("DbConnectionManager"));
			m_DataAccessManager = new DataAccessManager(config.GetConfiguration("DataAccessManager"), this);
		}

		public DbConnectionManager DbConnectionManager { get { return m_DbConnectionManager; } }
		public DataAccessManager DataAccessManager { get { return m_DataAccessManager; } }
		public Model Model { get { return m_Model; } }
		public Factory Factory { get { return m_Factory; } }
		public IdentityMap ParentIdentityMap { get { return m_IdentityMap.Parent; } set { m_IdentityMap.Parent = value; } }

		public ObjectId NewObjectId(Entity e, params object[] initValues)
		{
			Index pk = e.PrimaryKey;
			object[] values = new object[pk.Length];
			ObjectId oid = new ObjectId(e.ObjectType, values);
			for (int c = 0; c < initValues.Length; c++)
				oid[c] = initValues[c];
			return oid;
		}
		public object RegistrationLock { get { return m_IdentityMap; } }
		public void Register(Object obj) { m_IdentityMap.Register(obj); }
		public Object GetRegistered(ObjectId oid) { return m_IdentityMap.GetRegistered(oid); }
		public Object NewObject(Entity e, ObjectId oid)
		{
			Object obj = Activator.CreateInstance(e.Type, this) as Object;
			if (oid != null)
			{
				for (int c = 0; c < oid.Lenght; c++)
				{
					obj.ObjectId[c] = oid[c];
					Property p = e.PrimaryKey[c];
					p.SetValue(obj, oid[c]);
				}
			}
			return obj;
		}
		public T GetRegistered<T>(ObjectId oid) where T : Object { return (T)GetRegistered(oid); }
		public T NewObject<T>(Entity e, ObjectId oid) where T : Object { return (T)NewObject(e, oid); }

		public void Commit()
		{
			m_DataAccessManager.BeginTransaction();
			try
			{
				foreach (Object obj in m_NewObjects)
				{
					DataAccess dataAccess = m_DataAccessManager.GetDataAccess(obj.Entity);
					dataAccess.Create(obj);
					obj.State = ObjectState.Clean;
					m_IdentityMap.Register(obj);
				}
				m_NewObjects.Clear();
				foreach (Object obj in m_DirtyObjects)
				{
					DataAccess dataAccess = m_DataAccessManager.GetDataAccess(obj.Entity);
					obj.State = ObjectState.Clean;
					dataAccess.Update(obj);
				}
				m_DirtyObjects.Clear();
				foreach (Object obj in m_DeletedObjects)
				{
					DataAccess dataAccess = m_DataAccessManager.GetDataAccess(obj.Entity);
					dataAccess.Delete(obj.ObjectId);
					m_IdentityMap.Remove(obj.ObjectId);
					obj.State = ObjectState.Deleted;
				}
				m_DeletedObjects.Clear();

				m_DataAccessManager.CommitTransaction();
			}
			finally
			{
				m_DataAccessManager.RollbackTransaction();
			}
		}

		public void MarkNew(Object obj)
		{
			obj.State = ObjectState.New;
			m_NewObjects.Add(obj);
		}

		public void MarkDirty(Object obj)
		{
			obj.State = ObjectState.Dirty;
			m_DirtyObjects.Add(obj);
		}

		public void MarkDeleted(Object obj)
		{
			obj.State = ObjectState.Deleted;
			m_DeletedObjects.Add(obj);
		}

		public void Dispose()
		{
			m_IdentityMap = null;
			m_DeletedObjects = null;
			m_DirtyObjects = null;
			m_NewObjects = null;
			m_Model = null;
			m_Factory = null;
			if(m_DbConnectionManager != null)
				m_DbConnectionManager.Dispose();
			if(m_DataAccessManager != null)
				m_DataAccessManager.Dispose();
			m_DbConnectionManager = null;
			m_DataAccessManager = null;
		}
	}
}
